package net.chowda.castcluster.util;

import net.chowda.castcluster.VideoProvider;
import net.chowda.castcluster.provider.*;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.jdom.xpath.XPath;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*;

/**
 * this class is a sigleton which provides access to important config in:
 *
 *      ~/.castcluster/castcluster.xml
 *
 * the static constants should give you an idea of what's "important"
 */
public class CastClusterConfig {
    /**
     * currently this config item key refers to a value that is always the same: ~/.castcluster/
     *
     * ~ is computed using the "user.home" system property
     */
    public static final String CONFIG_BASE_DIR  = "configBaseDir";

    /**
     * this config key refers to the URL CastCluster will be available at. (e.g. http://localhost:8080/castcluster)
     */
    public static final String BASE_URL         = "baseUrl";

    /**
     * this is the poorly named key to the ffmpeg binary.
     */
    public static final String CONVERTER_PATH   = "converterPath";

    /**
     * this is the user name to use to log into the sharing server.
     */
    public static final String PUB_USER         = "publishUser";

    /**
     * this is the password to use to log into the sharing server.
     */
    public static final String PUB_PASSWORD     = "publishPass";

    private List<VideoProvider> providers           = new ArrayList<VideoProvider>(5);
    private Map<String, VideoProvider> providerMap  = new HashMap<String, VideoProvider>(5);
    private Map<String, String> propertyMap = null;

    private static String configDir = System.getProperty("user.home") + File.separator + ".castcluster";

    // this class is a singleton
    private static CastClusterConfig INSTANCE = null;
    private CastClusterConfig() {}

    public synchronized static CastClusterConfig getInstance() {
        if(INSTANCE == null) {
            try {
                System.err.println("loading config");
                INSTANCE = new CastClusterConfig();
                INSTANCE.init();
                INSTANCE.save();
            } catch (Exception e) {
                System.err.println("couldn't load config!");
                throw new RuntimeException(e);
            }
        }
        return INSTANCE;
    }

    public void save() {
        // I think this is safe and logical in a singleton... someone please correct me if I'm wrong.
        // we want to be pretty sure we're not writing to the file in multiple threads...
        // i'm happy to use optimistic concurency in this instance.
        synchronized(this) {
            Element root = new Element("config");

            // xmlify current global props
            Map<String, String> props = getGlobalProperties();
            Element globals = new Element("global");
            for (String propName : props.keySet()) {
                globals.addContent(new Element(propName).setText(props.get(propName)));
            }
            root.addContent(globals);

            XMLOutputter xmlOutputter = new XMLOutputter();
            xmlOutputter.setFormat(Format.getPrettyFormat());

            String base = getGlobalProp(CastClusterConfig.CONFIG_BASE_DIR);
            new File(base).mkdirs();
            try {
                // this filename is "hardcoded" because it doesn't really need to change, at least not easily.
                FileWriter fileWriter = new FileWriter(base + File.separator + "castcluster.xml");
                xmlOutputter.output(root, fileWriter);
                fileWriter.flush();
                fileWriter.close();
            } catch (IOException e) {
                throw new RuntimeException("couldn't write config!", e);
            }
        }
    }

    public List<VideoProvider> getProviders() {
        return providers;
    }

    public Map<String, VideoProvider> getProviderMap() {
        return providerMap;
    }

    public Map<String, String> getGlobalProperties() {
        return propertyMap;
    }

    public String getGlobalProp(String configKey) {
        return propertyMap.get(configKey);
    }

    private void init() {
        populateGlobals(getPlayListDOM());
        // these used to be in XML... but it was just silly overkill and a pain in the ass for upgrades.
        YouTubeProvider yt      = new YouTubeProvider();
        DirectoryProvider dir   = new DirectoryProvider();
        GoogleVideoProvider gv  = new GoogleVideoProvider();
        WebFeedProvider rss     = new WebFeedProvider();
        RevverProvider rv       = new RevverProvider();

        providers.addAll(Arrays.asList(yt, dir, gv, rss, rv));
        providerMap.put("yt",   yt);
        providerMap.put("dir",  dir);
        providerMap.put("gv",   gv);
        providerMap.put("rss",  rss);
        providerMap.put("rv",   rv);
    }

    private void populateGlobals(Document document) {
        try {
            propertyMap = new HashMap<String, String>();
            Element properties = (Element) XPath.selectSingleNode(document, "//global");
            propertyMap.put(CONFIG_BASE_DIR, configDir);
            for (Object o : properties.getChildren()) {
                String key = ((Element) o).getName();
                propertyMap.put(key, ((Element) o).getTextTrim());
            }
        } catch (JDOMException e) {
            throw new RuntimeException(e);
        }
    }

    private static Document getPlayListDOM() {
        try {
            return new SAXBuilder().build(getConfigFile());
        } catch (Exception e) {
            System.err.println("problem loading config!");
            throw new RuntimeException(e);
        }
    }

    private static File getConfigFile() throws Exception {
        File configFile = new File(configDir + File.separator + "castcluster.xml");
        if(!configFile.exists()) {
            // make sure the dir is there for when we do save the DOM.
            configFile.getParentFile().mkdirs();
            // we'll use the default config file if one does not exist yet.
            configFile = new File(Thread.currentThread().getContextClassLoader().getResource("castcluster.xml").toURI());
        }
        return configFile;
    }
}
